#ifdef __GNUG__
#pragma implementation
#endif

#include "Text.h"

#include "Class.h"
#include "Error.h"
#include "RegularExp.h"
#include "Mark.h"
#include "Port.h"
#include "OrdColl.h"
#include "Math.h"
#include "String.h"
#include "TextMarks.h"

const int
	cTextInitCap = 16,
	cEOT         = -1,
	cTabw        = 40;

//---- formating properties ----------------------------------------------------

byte TextCharMap[] = {
	cTextBreak|cTextPara,         /* 0x00 */
	0,                            /* 0x01 */
	0,                            /* 0x02 */
	0,                            /* 0x03 */
	0,                            /* 0x04 */
	0,                            /* 0x05 */
	0,                            /* 0x06 */
	0,                            /* 0x07 */
	0,                            /* 0x08 */
	0,                            /* 0x09 */
	cTextBreak|cTextPara,         /* 0x0a */
	0,                            /* 0x0b */
	cTextBreak,                   /* 0x0c */
	cTextBreak,                   /* 0x0d */
	0,                            /* 0x0e */
	0,                            /* 0x0f */
	0,                            /* 0x10 */
	0,                            /* 0x11 */
	0,                            /* 0x12 */
	0,                            /* 0x13 */
	0,                            /* 0x14 */
	0,                            /* 0x15 */
	0,                            /* 0x16 */
	0,                            /* 0x17 */
	0,                            /* 0x18 */
	0,                            /* 0x19 */
	0,                            /* 0x1a */
	0,                            /* 0x1b */
	0,                            /* 0x1c */
	0,                            /* 0x1d */
	0,                            /* 0x1e */
	0,                            /* 0x1f */
};

static TextChanges changeRec;

//---- Text --------------------------------------------------------------------

NewAbstractMetaImpl(Text,Object, (TP(marks), TP(observers), T(tabWidth)));

Text::Text()
{
	Init();
}

Text::~Text()
{
	if (marks) {
		marks->FreeAll();
		delete marks;
		marks= 0;
	}
	if (observers)
		CleanupObservers(observers);
}

void Text::Init()
{
	marks= 0;
	tabWidth= cTabw;
	observers= 0;
	paraStyle= new_ParaStyle();
	charStyle= gCharStyles->Default();
}

void Text::InitNew()
{
	Init();
}

int Text::GrowSize(int minSize)
{
	if (cMaxInt - minSize > minSize)
		return minSize+minSize;
	int d= minSize-Size();
	int m= Math::Min(2000, d);
	if (cMaxInt - Size() > m)
		return Size()+m;
	Error("GrowSize", "Cannot grow text");
	return 0;
}

void Text::ReplaceRange(int, int, Text*, int, int)
{
	AbstractMethod("ReplaceRange");
}

void Text::Cut(int from, int to)
{
	if (marks)
		marks->Replace(from, to, 0);
	Text *nullText= MakeScratchText(0, 0);
	ReplaceRange(from, to, nullText, 0, 0);
	delete nullText;
	Send(cIdNone, eTextDeleted, changeRec(from, to));
}

void Text::Paste(Text *src, int from, int to)
{
	if (marks)
		marks->Replace(from, to, src->End());
	ReplaceRange(from, to, src, 0, src->End());
	Send(cIdNone, eTextReplaced, changeRec(from, to, src->End()));
}

void Text::Copy(Text *dst, int from, int to)
{
	dst->ReplaceRange(0, dst->End(), this, from, to);
}

void Text::CopyInStr(byte*, int, int, int)
{
	AbstractMethod("CopyInStr");
}

void Text::Insert(byte c, int from, int to)
{
	Text *t= MakeScratchText(&c, 1);
	Paste(t, from, to);
	delete t;
}

void Text::InsertStr(int from, int to, byte *str, int len)
{
	if (len == -1 && str)
		len= strlen((char*)str);
	Text *t= MakeScratchText(str, len);
	Paste(t, from, to);
	delete t;
}

void Text::ReplaceWithStr(byte *buf, int len)
{
	InsertStr(0, End(), buf, len);
}

void Text::SetFStringVL(char *fmt, va_list va)
{
	byte *buf= (byte*)strvprintf(fmt, va);
	ReplaceWithStr(buf, -1);
	delete buf;
}

void Text::SetFString(char *va_(fmt), ...)
{
	va_list ap;
	va_start(ap,va_(fmt));
	SetFStringVL(va_(fmt), ap);
	va_end(ap);
}

int Text::Search(RegularExp *rex, int *nMatched, int start, int range, bool dir)
{
	if (dir == cSearchForward)
		return rex->SearchForward(AsString(), nMatched, start, Size(), range, 0);
	return rex->SearchBackward(AsString(), nMatched, start, Size(), range, 0);
}

bool Text::IsParaStart(int at)
{
	return (at <= 0) || IsPara((*this)[at-1]);
}

bool Text::IsParaEnd(int at)
{
	return (at >= Size()) || IsPara((*this)[at]);
}

Text *Text::MakeScratchText(byte *, int)
{
	AbstractMethod("MakeScratchText");
	return 0;
}

Text* Text::Save(int from, int to)
{
	if (!CheckRange(Size(), from, to))
		Error("Save", "Out of range from= %d to=%d size= %d", from, to, Size());
	Text *t= (Text*)this->New();
	Copy(t, from, to);
	return t;
}

void Text::Append(byte c)
{
	int at= Math::Max(0, End());
	Insert(c, at, at);
}

byte& Text::operator[](int)
{
	static byte dummyc= '\0';
	AbstractMethod("operator[]");
	return dummyc;
}

void Text::Empty()
{
	Cut(0, End());
}

int Text::Size()
{
	AbstractMethod("Size");
	return 0;
}

void Text::GetWordRange(int at, int *start, int *end)
{
	register int i;

	if (!CheckRange(Size(), at, at))
		return;

	for (i= at-1; i >= 0 && Isinword((*this)[i]); i--)
		;
	*start= i+1;
	for (i= at; i < Size() && Isinword((*this)[i]); i++)
		;
	*end= i;
}

void Text::GetParaRange(int at, int *start, int *end)
{
	register int i, ch;

	if (!CheckRange(Size(), at, at))
		return;

	for (i= at-1; i >= 0; i--) {
		ch= (*this)[i];
		if (IsPara(ch))
			break;
	}
	*start= i+1;
	for (i= at; i < Size(); i++) {
		ch= (*this)[i];
		if (IsPara(ch))
			break;
	}
	*end= Math::Min(Size(), i+1);
}

int Text::FindLastBreak(int from)
{
	for (int i= from-1; i >= 0; i--)
		if (IsBreak((*this)[i]))
			break;
	return i;
}

int Text::FindNextBreak(int from)
{
	for (int i= from; i < Size(); i++)
		if (IsBreak((*this)[i]))
			break;
	return i;
}

void Text::SetDefTab(int t)
{
	tabWidth= t;
}

int Text::GetDefTab()
{
	return tabWidth;
}

TextIter *Text::MakeIterator(int, int, void*)
{
	AbstractMethod("MakeIterator");
	return 0;
}

//---- style access

void Text::SetCharStyle(TxtCharProp what, int, int, const CharStyleSpec &st)
{
	charStyle= gCharStyles->ChangeProperty(charStyle, what, st);
	Send(cIdNone, eTextChangedRange, changeRec(0, Size()));
}

void Text::SetParaStyle(TxtParaProp what, int, int, const ParaDesc &pd)
{
	paraStyle= gParaStyles->ChangeProperty(paraStyle, what, pd);
	Send(cIdNone, eTextChangedRange, changeRec(0, Size()));
}

CharStyle *Text::GetCharStyle(int)
{
	return charStyle;
}

CharStyle *Text::GetCurrentCharStyle()
{
	return 0;
}

ParaStyle *Text::GetParaStyle(int)
{
	return paraStyle;
}

void Text::SetFont(Font *fd, int from, int to)
{
	SetCharStyle(eTxtCPFontAll, from, to, CharStyleSpec(fd));
}

Font *Text::GetFont(int)
{
	return charStyle->GetFont();
}

void Text::ResetCurrentCharStyle()
{
}

//---- TextPainter contract

int Text::GetNextFontChange(int, CharStyle *&sp)
{
	sp= charStyle;
	return cMaxInt;
}

byte Text::GetMarkChar()
{
	return 0;
}

VisualMark *Text::GetVisualMarkAt(int)
{
	return 0;
}

bool Text::IsVisualMark(int)
{
	return FALSE;
}

byte *Text::GetLineAccess(byte**, int, int)
{
	AbstractMethod("GetLine");
	return 0;
}

const char *Text::AsString()
{
	AbstractMethod("AsString");
	return 0;
}

OStream& Text::PrintOn(OStream &s)
{
	Object::PrintOn(s);
	return s << charStyle SP << paraStyle SP;
}

IStream& Text::ReadFrom(IStream &s)
{
	Object::ReadFrom(s);
	return s >> charStyle >> paraStyle;
}

OStream& Text::PrintOnAsPureText(OStream &s)
{
	AbstractMethod("PrintOnAsPureText");
	return s;
}

IStream& Text::ReadFromAsPureText(IStream &s, long)
{
	AbstractMethod("ReadFromAsPureText");
	return s;
}

bool Text::IsEmpty()
{
	return End() == 0;
}

void Text::AddMark(Mark *m)
{
	if (marks == 0)
		marks= new MarkList;
	marks->Add(m);
}

Mark *Text::RemoveMark(Mark *m)
{
	if (marks == 0)
		marks= new MarkList;
	return marks->Remove(m);
}

Iterator *Text::GetMarkIter()
{
	return GetMarkList()->MakeIterator();
}

MarkList *Text::GetMarkList()
{
	if (marks == 0)
		marks= new MarkList;
	return marks;
}

void Text::InspectorId(char *b, int s)
{
	for (int i= 0; i < Math::Min(s-1, Size()-1); i++)
		b[i]= (*this)[i];
	b[i]= '\0';
}

bool Text::IsEqual(Object *text)
{
	if (text == 0 || !text->IsKindOf(Text))
		return FALSE;
	return StrCmp(AsString(), text->AsString(), -1, gStdEncoding->SortMap()) == 0;
}

u_long Text::Hash()
{
	return strhash(AsString());
}

//---- observing

Collection *Text::MakeObserverColl()
{
	return observers= new OrdCollection(4);
}

Collection *Text::GetObservers()
{
	return observers;
}

void Text::DestroyObserverColl()
{
	SafeDelete(observers);
}

void Text::SetObserverColl(Collection *cp)
{
	SafeDelete(observers);
	observers= cp;
}

//----- TextIter ---------------------------------------------------------------

TextIter::TextIter(Text *s, int from, int to)
{
	ct= s;
	ce= Math::Max(from,0);
	upto= Math::Min(to,s->Size());
}

TextIter::~TextIter()
{
}

void TextIter::Reset(Text *s, int from, int to)
{
	ct= s;
	ce= Math::Max(from,0);
	upto= Math::Min(to,s->Size());
}

int TextIter::operator()(int*, LineDesc*)
{
	return cEOT;
}

int TextIter::GetPos()
{
	return ce;
}

Font *TextIter::FontAt(int)
{
	return ct->GetFont();
}

int TextIter::Unget()
{
	return ce= unget;
}

int TextIter::Token(int*, LineDesc*)
{
	return cEOT;
}

int TextIter::GetLastPos()  // get last position
{
	return unget;
}

void TextIter::SetPos(int newPos)
{
	ce= newPos;
}

//----- AutoTextIter -----------------------------------------------------------

AutoTextIter::~AutoTextIter()
{
	if (ti) {
		if ((char*)ti == space)
			ti->~TextIter();
		else
			delete ti;
		ti= 0;
	}
}

